package de.stefanocke.roo.wicket.templates

import de.stefanocke.roo.wicket.Stereotypes

class ListEditorTemplate extends JavaHtmlTemplate{
	
	override String javaClassNameWhenNew(){
		"ListEditorPanel"
	}
	
	override String stereotype(){
		Stereotypes::LIST_EDITOR
	}
	
	override subPackageToUseWhenNew(){
		"common"
	}
		
	override buildJavaContents(){
		import_("java.util.ArrayList")
		import_("java.util.List")
		
		import_("org.apache.wicket.Component")
		import_("org.apache.wicket.markup.html.form.Button")
		import_("org.apache.wicket.markup.html.form.IFormModelUpdateListener")
		import_("org.apache.wicket.markup.html.list.ListItem")
		import_("org.apache.wicket.markup.html.panel.Panel")
		import_("org.apache.wicket.markup.repeater.RepeatingView")
		import_("org.apache.wicket.model.AbstractReadOnlyModel")
		import_("org.apache.wicket.model.IModel")
		import_("org.apache.wicket.model.IObjectClassAwareModel")
		import_("org.apache.wicket.model.LoadableDetachableModel")
		
		val ILabeled = import_(typeof(ILabeledTemplate).generatedJavaType)
		'''
		/**
		 * A form component panel that allows to edit a list of beans, including adding
		 * or removing elements.
		 * <p>
		 * Loosely based on
		 * http://wicketinaction.com/2008/10/building-a-listeditor-form-component/
		 * 
		 * @author stoc
		 * 
		 * @param <T>
		 *            model type of the elements in the list
		 */
		«stereotypeAnnotation»
		public abstract class «simpleTypeName»<T> extends Panel implements IFormModelUpdateListener {
		
			/***/
			private static final long serialVersionUID = 1L;
		
			/**
			 * Represents the current state of the list that is edited. For example,
			 * this consists of some already existing entities and some freshly added.
			 * Since IModel is used here, the footprint is as small as possible. When
			 * using loadable / detachable entity models, only the IDs for the already
			 * existing entities are kept in the list.
			 */
			private List<IModel<? extends T>> list = new ArrayList<IModel<? extends T>>();
		
			private ListEditor listEditor;
		
			public ListEditorPanel(String id) {
				super(id);
				listEditor = new ListEditor("list");
				add(listEditor);
				Button addLink = new Button("add") {
					/***/
					private static final long serialVersionUID = 1L;
		
					// TODO: Zu starr für polymorphe listen... Es sollte möglich sein,
					// verschiedene subtypen hinzuzufügen.
					@Override
					public void onSubmit() {
						listEditor.addItem(createNewModel(getElementClass()));
					}
		
				}.setDefaultFormProcessing(false);
		
				add(addLink);
		
			}
		
			private class ListEditor extends RepeatingView {
		
				/***/
				private static final long serialVersionUID = 1L;
		
				public ListEditor(String id) {
					super(id);
				}
		
				@Override
				protected void onBeforeRender() {
					if (!hasBeenRendered()) {
						createItems();
					}
					super.onBeforeRender();
				}
		
				private void createItems() {
					for (int i = 0; i < list.size(); i++) {
						createItem(i);
					}
				}
		
				public void refreshItems() {
					this.removeAll();
					createItems();
				}
		
				private void createItem(final int index) {
					ListItem<IModel<? extends T>> li = new ListItem<IModel<? extends T>>(newChildId(), index,
							new AbstractReadOnlyModel<IModel<? extends T>>() {
		
								/**
								 * 
								 */
								private static final long serialVersionUID = 1L;
		
								@Override
								public IModel<? extends T> getObject() {
									return list.get(index);
								}
							});
					add(li);
					populateItem(li);
				}
		
				protected void populateItem(final ListItem<IModel<? extends T>> item) {
					Component itemComponent = ListEditorPanel.this.createItemComponent("item", item.getModelObject());
					item.add(itemComponent);
					if (itemComponent instanceof «ILabeled») {
						((«ILabeled») itemComponent).setLabel(getItemLabelModel(item));
					}
		
					Button removeLink = new Button("remove") {
						/***/
						private static final long serialVersionUID = 1L;
		
						@Override
						public void onSubmit() {
							int idx = item.getIndex();
		
							for (int i = idx + 1; i < item.getParent().size(); i++) {
								ListItem<?> it = (ListItem<?>) item.getParent().get(i);
								it.setIndex(it.getIndex() - 1);
							}
							ListEditor.this.remove(item);
							list.remove(idx);
		
						}
		
					}.setDefaultFormProcessing(false);
		
					item.add(removeLink);
		
				}
		
				public void addItem(IModel<? extends T> value) {
					list.add(value);
					createItem(list.size() - 1);
				}
		
			};
		
			@Override
			protected void onInitialize() {
				super.onInitialize();
				// This is the first time, the CompoundPropertyModel of the parent is
				// available.
				onModelChanged();
			}
		
			@Override
			protected void onModelChanged() {
		
				@SuppressWarnings("unchecked")
				IModel<List<T>> model = (IModel<List<T>>) getDefaultModel();
				List<T> modelObject = model.getObject();
		
				if (modelObject == null) {
					list = new ArrayList<IModel<? extends T>>();
				} else {
					list = new ArrayList<IModel<? extends T>>(modelObject.size());
		
					for (int i = 0; i < modelObject.size(); i++) {
						list.add(createElementModel(model, i));
					}
		
				}
				listEditor.refreshItems();
			}
		
			@SuppressWarnings("unchecked")
			@Override
			public void updateModel() {
				List<T> elementList = null;
				if (reuseExistingList()) {
					elementList = ((IModel<List<T>>) getDefaultModel()).getObject();
					if (elementList != null) {
						elementList.clear();
					}
				}
				if (elementList == null) {
					elementList = new ArrayList<T>();
				}
				for (IModel<? extends T> model : list) {
					T e = (T) model.getObject();
					if (e != null || !removeNullValues()) {
						elementList.add(e);
					}
				}
				setDefaultModelObject(elementList);
			};
		
			/**
			 * 
			 * @return true means that the list in the model is reused if it already
			 *         exists. false means, a new list is created on updateModel
			 */
			protected boolean reuseExistingList() {
				return true;
			}
		
			/**
			 * Whether null element values shall be removed before the model is updated.
			 * Default is true.
			 * 
			 * @return
			 */
			protected boolean removeNullValues() {
				return true;
			}
		
			@Override
			protected void onDetach() {
				super.onDetach();
				// detach each list item
				for (IModel<? extends T> item : list) {
					item.detach();
				}
			}
		
			protected IModel<String> getItemLabelModel(final ListItem<IModel<? extends T>> item) {
				return new AbstractReadOnlyModel<String>() {
		
					/***/
					private static final long serialVersionUID = 1L;
		
					@Override
					public String getObject() {
						return getItemLabel(item);
					}
		
				};
			}
		
			protected String getItemLabel(final ListItem<IModel<? extends T>> item) {
				// TODO: It is very implicit to assume the parent provides the item
				// label...
				return getLocalizer().getString(getParent().getId(), ListEditorPanel.this) + " " + (item.getIndex() + 1);
			}
		
			protected Component createItemComponent(String id, IModel<? extends T> model) {
				Component c = createItemComponent(id);
				if (c == null) {
					throw new IllegalArgumentException("Item Component is null. Forgotten to override createItemComponent?");
				}
				c.setDefaultModel(model);
				return c;
			}
		
			protected Component createItemComponent(String id) {
				return null;
			}
		
			/**
			 * Creates a model for a new element to be added to the list. The default
			 * implementation creates a LoadableDetachableModel that calls
			 * createNewElement
			 * 
			 * @return
			 */
			protected IModel<T> createNewModel(final Class<T> clazz) {
		
				return new LoadabaleTypeAwareModel<T>() {
					/***/
					private static final long serialVersionUID = 1L;
		
					@Override
					protected T load() {
						return null;
					}
		
					@SuppressWarnings("unchecked")
					@Override
					public Class<T> getObjectClass() {
						if (getObject() != null) {
							return (Class<T>) getObject().getClass();
						} else {
							return clazz;
						}
					}
				};
			}
		
			private static abstract class LoadabaleTypeAwareModel<T> extends LoadableDetachableModel<T> implements
					IObjectClassAwareModel<T> {
		
				/***/
				private static final long serialVersionUID = 1L;
			}
		
			/**
			 * The class of new elements to be created, when add button is clicked.
			 * Default is null, which means that the type is determined by examining the
			 * model object.
			 * 
			 * @return
			 */
			protected Class<T> getElementClass() {
				return null;
			};
		
			/**
			 * Create a model for an element that exists in the original list to be
			 * edited. The default implementation identifies / loads the list elements
			 * based on their index.
			 * For entities, one might to wish to override this to use the IDs instead.
			 * (Which is more fail-safe when the one-to-many relationship is not really
			 * ordered or one might not depend on optimistic locking
			 * in the parent to detect concurrent modification of the list)
			 * 
			 * @param model
			 *            the model of the list
			 * @param index
			 *            the index of the element in the list
			 * @return the Model for the element
			 */
			protected IModel<T> createElementModel(final IModel<List<T>> model, final int index) {
		
				return new LoadabaleTypeAwareModel<T>() {
		
					private static final long serialVersionUID = 1L;
		
					@Override
					protected T load() {
						// TODO: error handling if index does not exist anymore (due to
						// some concurrent modifications)
						return model.getObject().get(index);
					}
		
					@SuppressWarnings("unchecked")
					@Override
					public Class<T> getObjectClass() {
						if (getObject() != null) {
							return (Class<T>) getObject().getClass();
						} else {
							return getElementClass();
						}
					}
		
				};
			}
		
		}

		'''
	}
	
	override buildHtmlBody(){
		'''
		<wicket:panel xmlns:wicket="http://wicket.sourceforge.net/">
			<div>
				<div class="fieldset" wicket:id="list">				
					<span wicket:id="item"></span>				
					<input type="submit" value="Remove"  wicket:id="remove"></input>	
				</div>
				<fieldset class="submit">	
		   			<input type="submit" value="Add" wicket:id="add"></input>
		   		</fieldset>
			</div>
		</wicket:panel>
		'''
	}
}